package com.arpa.wms.common.util;

import cn.hutool.core.date.DateTime;
import cn.hutool.core.date.DateUtil;
import cn.hutool.crypto.digest.DigestUtil;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.springframework.util.Assert;

import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.util.*;

/**
 * 签名校验工具类
 * @Author liuyj
 * @Date 2020-11-10 14:18:10
 */
@Slf4j
public class SignatureUtils {
    /**
     * 15分钟有效期
     */
    private final static long MAX_EXPIRE = 15;

    /** 随机字符串 **/
    public static String SIGN_NONCE_KEY="SIGN_NONCE";
    /** 时间戳 yyyyMMddHHmmss **/
    public static String SIGN_TIMESTAMP_KEY="SIGN_TIMESTAMP";
    /** 签名类型 MD5 SHA256 **/
    public static String SIGN_SIGN_TYPE_KEY="SIGN_TYPE";
    /** 签名值 **/
    public static String SIGN_SIGN_KEY="SIGN_VALUE";
    /**签名秘钥**/
    public static String SIGN_SECRET_KEY="SIGN_SECRET";
    /**
     * 验证参数
     *
     * @param paramsMap
     * @throws Exception
     */
    public static void validateParams(Map<String, Object> paramsMap) throws Exception {
        Assert.notNull(paramsMap.get(SIGN_NONCE_KEY), "签名验证失败:SIGN_NONCE不能为空");
        Assert.notNull(paramsMap.get(SIGN_TIMESTAMP_KEY), "签名验证失败:SIGN_TIMESTAMP不能为空");
        Assert.notNull(paramsMap.get(SIGN_SIGN_TYPE_KEY), "签名验证失败:SIGN_TYPE不能为空");
        Assert.notNull(paramsMap.get(SIGN_SIGN_KEY), "签名验证失败:SIGN_VALUE不能为空");
        if (!SignType.contains(paramsMap.get(SIGN_SIGN_TYPE_KEY).toString())) {
            throw new IllegalArgumentException(String.format("签名验证失败:SIGN_TYPE必须为:%s,%s", SignType.MD5, SignType.SHA256));
        }
        try {
            DateTime da = DateUtil.parse(paramsMap.get(SIGN_TIMESTAMP_KEY).toString(),"yyyyMMddHHmmss");
        } catch (Exception e) {
            throw new IllegalArgumentException("签名验证失败:TIMESTAMP格式必须为:yyyyMMddHHmmss");
        }
        String timestamp = paramsMap.get(SIGN_TIMESTAMP_KEY).toString();
        //当前服务器的前后MAX_EXPIRE分钟
        LocalDateTime qian = LocalDateTime.now().minusMinutes(MAX_EXPIRE);
        LocalDateTime hou = LocalDateTime.now().plusMinutes(MAX_EXPIRE);
        LocalDateTime giveDt = LocalDateTime.parse(timestamp, DateTimeFormatter.ofPattern("yyyyMMddHHmmss"));
        //判断时间戳 timestamp=201808091113
        if(giveDt.isBefore(qian) || giveDt.isAfter(hou) ){
            throw new IllegalArgumentException("签名验证失败:SIGN_TIMESTAMP已过期");
        }
    }

    /**
     * @param paramsMap    必须包含
     * @param clientSecret
     * @return
     */
    public static boolean validateSign(Map<String, Object> paramsMap, String clientSecret) {
        try {
            validateParams(paramsMap);
            String sign = paramsMap.get(SIGN_SIGN_KEY).toString();
            //重新生成签名
            String signNew = getSign(paramsMap, clientSecret);
            //判断当前签名是否正确
            if (signNew.equals(sign)) {
                return true;
            }
            log.error("验签失败，签名值不匹配");
        } catch (Exception e) {
            log.error("验签失败:{}", e.getMessage());
            return false;
        }
        return false;
    }


    /**
     * 得到签名
     * 签名值 SIGN_VALUE 不参与签名
     * SIGN_SECRET 秘钥是参与签名的，但是paramMap可以不包含
     * 必须包含 SIGN_TYPE = SHA256|MD5 签名方式  SIGN_TIMESTAMP=时间戳 SIGN_NONCE=随机字符串
     * @param paramMap
     * @param signSecret 验证接口的clientSecret
     * @return
     */
    public static String getSign(Map<String, Object> paramMap, String signSecret) {
        if (paramMap == null) {
            return "";
        }
        //排序
        Set<String> keySet = paramMap.keySet();
        String[] keyArray = keySet.toArray(new String[keySet.size()]);
        Arrays.sort(keyArray);
        StringBuilder sb = new StringBuilder();
        String signType = paramMap.get(SIGN_SIGN_TYPE_KEY).toString();
        SignType type = null;
        if (StringUtils.isNotBlank(signType)) {
            type = SignType.valueOf(signType);
        }
        if (type == null) {
            type = SignType.MD5;
        }
        for (String k : keyArray) {
            //排除 验签值，秘钥
            if (k.equals(SIGN_SIGN_KEY) || k.equals(SIGN_SECRET_KEY)) {
                continue;
            }
            if (paramMap.get(k)!=null && paramMap.get(k).toString().trim().length() > 0) {
                // 参数值为空，则不参与签名
                sb.append(k).append("=").append(paramMap.get(k).toString().trim()).append("&");
            }
        }
        //签名秘钥不从paramMap获取
        sb.append(SIGN_SECRET_KEY + "=").append(signSecret);
        String signStr = "";
        //加密
        switch (type) {
            case MD5:
                signStr = DigestUtil.md5Hex(sb.toString()).toUpperCase();
                break;
            case SHA256:
                signStr = DigestUtil.sha256Hex(sb.toString()).toUpperCase();
                break;
            default:
                break;
        }
        return signStr;
    }

    /**
     * 签名类型
     */
    public enum SignType {
        MD5,
        SHA256;
        public static boolean contains(String type) {
            for (SignType typeEnum : SignType.values()) {
                if (typeEnum.name().equals(type)) {
                    return true;
                }
            }
            return false;
        }
    }

}

